package com.tcm.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.tcm.common.enums.CachePrefix;
import com.tcm.config.AppProperties;
import com.tcm.entity.*;
import com.tcm.entity.bo.StudentDailyExerciseBo;
import com.tcm.entity.bo.UserCacheBo;
import com.tcm.entity.dto.CorrectExerciseDto;
import com.tcm.entity.dto.ItemCorrectDto;
import com.tcm.entity.dto.ItemPackageDto;
import com.tcm.entity.dto.ScoreUpdateDto;
import com.tcm.entity.vo.BookListResult;
import com.tcm.entity.vo.ItemCorrectResp;
import com.tcm.entity.vo.TopicResp;
import com.tcm.entity.vo.TopicRespPage;
import com.tcm.exception.DatabaseOperationException;
import com.tcm.exception.EntityInvalidException;
import com.tcm.mapper.BookItemMapper;
import com.tcm.mapper.ItemMapper;
import com.tcm.mapper.TestPaperDetailMapper;
import com.tcm.service.*;
import com.tcm.utils.ItemUtil;
import com.tcm.utils.RedisUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 学生题目业务实现类
 * @author Guqier
 * @version 1.0
 * @date 2023/8/20 20:35
 */
@Service
public class ItemServiceImpl implements ItemService {

    @Autowired
    private CodeService codeService;

    @Autowired
    private ClozeItemService clozeItemService;

    @Autowired
    private SubjectItemService subjectItemService;

    @Autowired
    private BookItemMapper bookItemMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private CollectService collectService;

    @Autowired
    private ImageService imageService;

    @Autowired
    private TestPaperDetailMapper testPaperDetailMapper;

    @Autowired
    @Lazy
    private ItemStudentService itemStudentService;

    @Autowired
    private BookService bookService;

    @Autowired
    private ScoreService scoreService;

    @Autowired
    private ExerciseService exerciseService;

    @Autowired
    private ExerciseHisService exerciseHisService;

    @Autowired
    private ItemMapper itemMapper;

    /**
     * 根据章节获取题目
     * @param chapterId  章节ID
     * @param current    当前题目数
     * @param currUserId 当前登录用户ID
     * @return 题目信息
     */
    @Override
    public TopicRespPage getItemsByChapter(Long chapterId, Integer current, Long currUserId) {
        // 获取当前章节下的所有题目
        LambdaQueryWrapper<BookItem> bookItemLambdaQueryWrapper = new LambdaQueryWrapper<>();
        bookItemLambdaQueryWrapper.eq(BookItem::getBookId, chapterId);
        bookItemLambdaQueryWrapper.orderByAsc(BookItem::getCreateTime);// 根据创建时间排序
        List<BookItem> bookItems = bookItemMapper.selectList(bookItemLambdaQueryWrapper);

        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 构造结果集
        return this.packageTopicRespPageByCurrent(
                current,
                bookItems.stream().map(bookItem -> {
                    ItemPackageDto dto = new ItemPackageDto();
                    BeanUtils.copyProperties(bookItem, dto);
                    return dto;
                }).collect(Collectors.toList()),
                userCacheBo.getAccount());
    }

    /**
     * 根据试卷获取题目
     * @param testPaperId 试卷ID
     * @param current     当前题号
     * @param currUserId  当前登录用户ID
     * @return 题目信息
     */
    @Override
    public TopicRespPage getItemsByTestPaper(Long testPaperId, Integer current, Long currUserId) {
        // 获取试卷的所有题目
        List<TestPaperDetail> testPaperDetails = testPaperDetailMapper.selectList(new LambdaQueryWrapper<TestPaperDetail>().eq(TestPaperDetail::getTestPaperId, testPaperId).orderByAsc(TestPaperDetail::getSequence));
        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 构造结果集
        List<ItemPackageDto> dtos = testPaperDetails.stream().map(testPaperDetail -> {
            ItemPackageDto dto = new ItemPackageDto();
            BeanUtils.copyProperties(testPaperDetail, dto);
            return dto;
        }).collect(Collectors.toList());
        return this.packageTopicRespPageByCurrent(current, dtos, userCacheBo.getAccount());
    }

    /**
     * 根据用户获取每日一练题目
     * @param currUserId 当前登录用户ID
     * @return 每日一练题目
     */
    @Override
    public TopicRespPage getItemsByDailyExercise(Long currUserId) {
        // 检查缓存中是否存在
        StudentDailyExerciseBo BO = redisUtil.getCacheObject(CachePrefix.STU_DAILY_EXERCISE_CACHE.getCacheKey(currUserId.toString()));
        List<ItemPackageDto> dtoList;
        if (Objects.nonNull(BO)) {
            if (BO.getIsComplete().equals(1)) {
                // 如果已完成则返回空
                return null;
            }
            dtoList = BO.getDtos();
        } else {
            Random random = new Random();
            // 查找当前学生拥有权限的所有书籍子节点
            List<BookListResult> stuCourse = bookService.getStuCourse(currUserId);
            List<Book> bookList = bookService.getBookList();
            List<Book> childNodes = new ArrayList<>();
            for (BookListResult book : stuCourse) {
                childNodes.addAll(bookService.findChildNode(book.getId(), bookList));
            }
            // 查找符合条件的题目
            List<Long> bookIds = childNodes.stream().map(Book::getId).collect(Collectors.toList());
            List<BookItem> bookItems = bookItemMapper.selectList(new LambdaQueryWrapper<BookItem>().in(!CollectionUtils.isEmpty(bookIds), BookItem::getBookId, bookIds).apply(CollectionUtils.isEmpty(bookIds), "1 != 1"));
            // 随机获取五道题
            List<ItemPackageDto> dtos = new ArrayList<>();
            while (dtos.size() < 5) {
                BookItem bookItem = bookItems.get(random.nextInt(bookItems.size()));
                if (!dtos.stream().map(ItemPackageDto::getItemId).collect(Collectors.toList()).contains(bookItem.getItemId())) {
                    ItemPackageDto dto = new ItemPackageDto();
                    dto.setItemId(bookItem.getItemId());
                    dto.setType(bookItem.getType());
                    dtos.add(dto);
                }
            }
            // 构造缓存封装类
            StudentDailyExerciseBo bo = new StudentDailyExerciseBo();
            bo.setDtos(dtos);
            bo.setIsComplete(0);
            // 将每日一练信息存入缓存
            redisUtil.setCacheObject(CachePrefix.STU_DAILY_EXERCISE_CACHE.getCacheKey(currUserId.toString()), bo, getTimeToTheMidnight(), TimeUnit.SECONDS);
            dtoList = dtos;
        }
        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 构造结果集
        return this.packageTopicRespPageBatch(dtoList, userCacheBo.getAccount());
    }

    /**
     * 根据课程ID获取该课程下所有题目ID
     * @param courseId 课程ID
     * @return 该课程下所有题目ID
     */
    private List<BookItem> getBookItemsByCourseId(Long courseId){
        // 查询当前课程下的所有节点
        List<Book> childNodeList = bookService.getChildBooks(courseId);
        // 获取题目ID列表
        return bookItemMapper.selectList(new LambdaQueryWrapper<BookItem>()
                .in(!CollectionUtils.isEmpty(childNodeList), BookItem::getBookId, childNodeList.stream().map(Book::getId).collect(Collectors.toList()))
                .apply(CollectionUtils.isEmpty(childNodeList), "1 != 1")
                .orderByAsc(BookItem::getCreateTime));
    }

    /**
     * 根据难度获取题目
     * @param difficultyId 难度ID
     * @param courseId 课程ID
     * @param current 当前题目数
     * @param currUserId 当前登录用户ID
     * @return 题目
     */
    @Override
    public TopicRespPage getItemsByDifficulty(Long difficultyId, Long courseId, Integer current, Long currUserId){
        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        List<BookItem> bookItems = getBookItemsByCourseId(courseId);
        List<Long> itemIds = bookItems.stream().map(BookItem::getItemId).collect(Collectors.toList());
        // 获取当前难度下的题目
        ItemPackageDto dto = itemMapper.getItemsByDifficulty(difficultyId, current - 1, !CollectionUtils.isEmpty(itemIds) ? itemIds : null);
        Integer total = itemMapper.getItemCountByDifficulty(difficultyId, !CollectionUtils.isEmpty(itemIds) ? itemIds : null);
        return packageTopicRespPageByCurrent(current, total, dto, userCacheBo.getAccount());
    }

    /**
     * 随机获取题目
     * @param courseId 课程ID
     * @param currUserId 当前用户ID
     * @return 题目信息
     */
    @Override
    public TopicRespPage getItemsByRandom(Long courseId, Long currUserId){
        // 获取题目ID列表
        List<BookItem> bookItems = getBookItemsByCourseId(courseId);
        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        Random random = new Random();
        int i = random.nextInt(bookItems.size());
        ItemPackageDto dto = new ItemPackageDto();
        BeanUtils.copyProperties(bookItems.get(i), dto);
        return packageTopicRespPageByCurrent(i + 1, bookItems.size(), dto, userCacheBo.getAccount());
    }

    /**
     * 根据类型获取题目
     * @param current 当前题目数
     * @param courseId 课程ID
     * @param type 题目类型
     * @param currUserId 当前用户ID
     * @return 题目信息
     */
    @Override
    public TopicRespPage getItemsByType(Integer current, Long courseId, Integer type, Long currUserId){
        // 获取题目ID列表
        List<BookItem> bookItems = getBookItemsByCourseId(courseId);
        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 过滤指定类型
        List<ItemPackageDto> dtos = bookItems.stream()
                .filter(bookItem -> bookItem.getType().equals(type))
                .map(bookItem -> {
                    ItemPackageDto dto = new ItemPackageDto();
                    dto.setItemId(bookItem.getItemId());
                    dto.setType(bookItem.getType());
                    return dto;
                })
                .collect(Collectors.toList());
        return packageTopicRespPageByCurrent(current, dtos, userCacheBo.getAccount());
    }

    /**
     * 计算题目总分
     * @param item 题目实体
     * @return 题目总分
     */
    @Override
    public Integer calculateItemTotalScore(Object item) {
        int score = 0;
        if (item instanceof SubjectItem) {
            SubjectItem subjectItem = (SubjectItem) item;
            Map<Long, Integer> scoreMap = AppProperties.ITEM_SCORE_SUBJECT;// 获取主观题分数矩阵
            if (StringUtils.hasText(subjectItem.getAnsDiagnose())) {
                score += scoreMap.get(subjectItem.getDifficultyId());
            }
            if (StringUtils.hasText(subjectItem.getAnsCause())) {
                score += scoreMap.get(subjectItem.getDifficultyId());
            }
            if (StringUtils.hasText(subjectItem.getAnsMethod())) {
                score += scoreMap.get(subjectItem.getDifficultyId());
            }
            if (StringUtils.hasText(subjectItem.getAnsPrescription())) {
                score += scoreMap.get(subjectItem.getDifficultyId());
            }
            if (StringUtils.hasText(subjectItem.getAnsMedicine())) {
                score += scoreMap.get(subjectItem.getDifficultyId());

            }
        } else if (item instanceof ClozeItem) {
            ClozeItem clozeItem = (ClozeItem) item;
            Map<Long, Map<String, Integer>> scoreMap = AppProperties.ITEM_SCORE_CLOZE;// 获取填空题分数矩阵
            String[] ans = clozeItem.getAns().split("\\|");
            for (String s : ans) {
                String rank = s.substring(0, 1);// 获取答案难度
                score += scoreMap.get(clozeItem.getDifficultyId()).get(rank);
            }
        }
        return score;
    }

    /**
     * 封装单个题目信息
     * @param current 当前题目数
     * @param dtos    题目信息DTO
     * @param account 当前用户账号
     * @return TopicRespPage
     */
    private TopicRespPage packageTopicRespPageByCurrent(Integer current, List<ItemPackageDto> dtos, String account) {
        // 构造结果集
        TopicRespPage topicRespPage = new TopicRespPage();
        topicRespPage.setTopics(Collections.emptyList());
        topicRespPage.setCurrent(current);

        // 判断题目是否为空
        if (CollectionUtils.isEmpty(dtos)) {
            topicRespPage.setTotal(0);
            return topicRespPage;
        }
        topicRespPage.setTotal(dtos.size());

        // 判断当前数是否超过题目总数
        if (current > dtos.size() || current < 1) {
            return topicRespPage;
        }

        // 根据当前题目数获取题目
        List<TopicResp> topicResps = new ArrayList<>();
        ItemPackageDto dto = dtos.get(current - 1);
        topicResps.add(this.packageTopicResp(dto, account));
        topicRespPage.setTopics(topicResps);
        return topicRespPage;
    }

    /**
     * 封装单个题目信息
     * @param current 当前题目数
     * @param total 题目总数
     * @param dto    题目信息DTO
     * @param account 当前用户账号
     * @return TopicRespPage
     */
    private TopicRespPage packageTopicRespPageByCurrent(Integer current, Integer total, ItemPackageDto dto, String account){
        // 构造结果集
        TopicRespPage topicRespPage = new TopicRespPage();
        topicRespPage.setCurrent(current);
        topicRespPage.setTotal(total);
        if (Objects.isNull(dto)){
            return topicRespPage;
        }
        topicRespPage.setTopics(new ArrayList<TopicResp>(){{
            add(packageTopicResp(dto, account));
        }});
        return topicRespPage;
    }

    /**
     * 批量封装题目信息
     * @param dtos    题目DTO列表
     * @param account 当前登录用户账号
     * @return 题目分页信息响应封装类
     */
    private TopicRespPage packageTopicRespPageBatch(List<ItemPackageDto> dtos, String account) {
        // 构造结果集
        TopicRespPage topicRespPage = new TopicRespPage();
        topicRespPage.setTopics(Collections.emptyList());
        topicRespPage.setCurrent(-1);
        topicRespPage.setTotal(5);
        // 判断题目是否为空
        if (CollectionUtils.isEmpty(dtos)) {
            topicRespPage.setTotal(0);
            return topicRespPage;
        }
        List<TopicResp> topicResps = new ArrayList<>();
        for (ItemPackageDto dto : dtos) {
            topicResps.add(this.packageTopicResp(dto, account));
        }
        topicRespPage.setTopics(topicResps);
        return topicRespPage;
    }

    /**
     * 封装单个题目响应结果
     * @param dto     题目DTO
     * @param account 当前用户账号
     * @return 题目封装类
     */
    private TopicResp packageTopicResp(ItemPackageDto dto, String account) {
        TopicResp topicResp = new TopicResp();
        topicResp.setType(dto.getType());
        if (dto.getType().equals(AppProperties.ITEM_TYPE_SUBJECT)) {
            SubjectItem subjectItem = subjectItemService.getSubjectItemById(dto.getItemId());
            if (Objects.isNull(subjectItem)) {
                throw new EntityInvalidException("题目不存在");
            }
            topicResp.setId(subjectItem.getId());
            topicResp.setGender(subjectItem.getGender());
            topicResp.setAge(subjectItem.getAge());
            // 查询难易级别
            Code difficulty = codeService.getCodeById(subjectItem.getDifficultyId());
            topicResp.setDifficulty(difficulty != null ? difficulty.getName() : null);
            topicResp.setKnowledgePoints(Arrays.asList(subjectItem.getKnowledgePoint().split("\\|")));
            topicResp.setContent(subjectItem.getContent());
            // 查询是否收藏
            LambdaQueryWrapper<Collect> collectLambdaQueryWrapper = new LambdaQueryWrapper<>();
            collectLambdaQueryWrapper.eq(Collect::getItemId, subjectItem.getId()).eq(Collect::getAccount, account);
            topicResp.setIsCollect(collectService.count(collectLambdaQueryWrapper) != 0 ? 1 : 0);
            // 查询关联图片
            List<Image> images = imageService.getImagesByItemId(subjectItem.getId());
            topicResp.setImages(images.stream().map(Image::getRemoteFileUrl).collect(Collectors.toList()));
            // 构造问题，计算总分
            List<TopicResp.SubjectQuestion> questions = new ArrayList<>();
            if (StringUtils.hasText(subjectItem.getAnsDiagnose())) {
                questions.add(new TopicResp.SubjectQuestion(AppProperties.SUBJECT_ANS_HEADING_DIAGNOSE, "请诊断该患者的病症是什么？", Arrays.asList(subjectItem.getTipDiagnose().split("\\|"))));
            }
            if (StringUtils.hasText(subjectItem.getAnsCause())) {
                questions.add(new TopicResp.SubjectQuestion(AppProperties.SUBJECT_ANS_HEADING_CAUSE, "请问该患者的病机是什么？", Arrays.asList(subjectItem.getTipCause().split("\\|"))));
            }
            if (StringUtils.hasText(subjectItem.getAnsMethod())) {
                questions.add(new TopicResp.SubjectQuestion(AppProperties.SUBJECT_ANS_HEADING_METHOD, "请问该患者的治法是什么？", Arrays.asList(subjectItem.getTipMethod().split("\\|"))));
            }
            if (StringUtils.hasText(subjectItem.getAnsPrescription())) {
                questions.add(new TopicResp.SubjectQuestion(AppProperties.SUBJECT_ANS_HEADING_PRESCRIPTION, "请问该患者开的处方是什么？", Arrays.asList(subjectItem.getTipPrescription().split("\\|"))));
            }
            if (StringUtils.hasText(subjectItem.getAnsMedicine())) {
                questions.add(new TopicResp.SubjectQuestion(AppProperties.SUBJECT_ANS_HEADING_MEDICINE, "请问该患者开的药物是什么？", Arrays.asList(subjectItem.getTipMedicine().split("\\|"))));
            }
            topicResp.setQuestions(questions);
            topicResp.setScore(calculateItemTotalScore(subjectItem));
        } else {
            ClozeItem clozeItem = clozeItemService.getClozeItemById(dto.getItemId());
            if (Objects.isNull(clozeItem)) {
                throw new EntityInvalidException("题目不存在");
            }
            topicResp.setId(clozeItem.getId());
            // 查询难易级别
            Code difficulty = codeService.getById(clozeItem.getDifficultyId());
            topicResp.setDifficulty(difficulty != null ? difficulty.getName() : null);
            topicResp.setKnowledgePoints(Arrays.asList(clozeItem.getKnowledgePoint().split("\\|")));
            topicResp.setContent(clozeItem.getMarkContent());
            // 查询是否收藏
            LambdaQueryWrapper<Collect> collectLambdaQueryWrapper = new LambdaQueryWrapper<>();
            collectLambdaQueryWrapper.eq(Collect::getItemId, clozeItem.getId()).eq(Collect::getAccount, account);
            topicResp.setIsCollect(collectService.count(collectLambdaQueryWrapper) != 0 ? 1 : 0);
            // 查询关联图片
            List<Image> images = imageService.getImagesByItemId(clozeItem.getId());
            topicResp.setImages(images.stream().map(Image::getRemoteFileUrl).collect(Collectors.toList()));
            // 计算总分
            topicResp.setScore(calculateItemTotalScore(clozeItem));
        }
        return topicResp;
    }

    /**
     * 批量评判按章节练习题目
     * @param dtos       题目答案封装列表
     * @param currUserId 当前登录用户ID
     * @return 评判结果集
     * @throws JsonProcessingException Json序列化异常
     */
    @Override
    @Transactional
    public List<ItemCorrectResp> correctItemBatch(List<ItemCorrectDto> dtos, Long currUserId, Integer type) throws JsonProcessingException {
        // 构造结果集
        List<ItemCorrectResp> respList = new ArrayList<>();
        // 判题并给出分数
        for (ItemCorrectDto dto : dtos) {
            ItemCorrectResp resp;
            if (dto.getType().equals(AppProperties.ITEM_TYPE_SUBJECT)) {// 判断题目类型
                resp = this.correctSubjectItem(dto);
            } else {
                resp = this.correctClozeItem(dto, AppProperties.CLOZE_ITEM_ANS_RANK_ALL);
            }
            respList.add(resp);
            // 写入答题记录并且更新学生积分
            boolean b = itemStudentService.saveItemStudent(resp, currUserId, type);
            if (!b) {
                throw new DatabaseOperationException("保存答题记录失败");
            }
        }
        return respList;
    }

    /**
     * 评判主观题
     * @param dto 题目答案数据封装
     * @return 评判结果
     */
    @Override
    public ItemCorrectResp correctSubjectItem(ItemCorrectDto dto) {
        // 获得分数矩阵
        Map<Long, Integer> scoreMap = AppProperties.ITEM_SCORE_SUBJECT;
        // 查找题目的实体类
        SubjectItem subjectItem = subjectItemService.getSubjectItemById(dto.getItemId());
        if (Objects.isNull(subjectItem)) {
            throw new EntityInvalidException("题目不存在");
        }
        // 构造结果集
        ItemCorrectResp resp = new ItemCorrectResp();
        BeanUtils.copyProperties(dto, resp, "questions");
        resp.setContent(subjectItem.getContent());
        resp.setAnalysis(subjectItem.getAnalysis());

        // 匹配答案并计算分数
        int score = 0;// 分数计数器
        List<ItemCorrectDto.Question> questions = dto.getQuestions();
        List<ItemCorrectResp.Question> questionList = new ArrayList<>();
        if (StringUtils.hasText(subjectItem.getAnsDiagnose())) {
            // 判断学生答案是否有该问
            Optional<ItemCorrectDto.Question> questionOptional = questions.stream().filter(question -> question.getHeading().equals(AppProperties.SUBJECT_ANS_HEADING_DIAGNOSE)).findFirst();
            // 获取该问标准答案
            String ansDiagnose = subjectItem.getAnsDiagnose();
            // 将该问学生答案和标准答案添加到响应结果集
            questionList.add(new ItemCorrectResp.Question(AppProperties.SUBJECT_ANS_HEADING_DIAGNOSE, questionOptional.map(ItemCorrectDto.Question::getAns).orElse(null), ansDiagnose));
            if (questionOptional.isPresent()) {
                // 判断答案是否正确并计算得分
                ItemCorrectDto.Question question = questionOptional.get();
                if (this.checkSubjectAns(question.getAns(), ansDiagnose)) {
                    score += scoreMap.get(subjectItem.getDifficultyId());
                }
            }
        }
        if (StringUtils.hasText(subjectItem.getAnsCause())) {
            Optional<ItemCorrectDto.Question> questionOptional = questions.stream().filter(question -> question.getHeading().equals(AppProperties.SUBJECT_ANS_HEADING_CAUSE)).findFirst();
            String ansCause = subjectItem.getAnsCause();
            questionList.add(new ItemCorrectResp.Question(AppProperties.SUBJECT_ANS_HEADING_CAUSE, questionOptional.map(ItemCorrectDto.Question::getAns).orElse(null), ansCause));
            if (questionOptional.isPresent()) {
                ItemCorrectDto.Question question = questionOptional.get();
                if (this.checkSubjectAns(question.getAns(), ansCause)) {
                    score += scoreMap.get(subjectItem.getDifficultyId());
                }
            }
        }
        if (StringUtils.hasText(subjectItem.getAnsMethod())) {
            Optional<ItemCorrectDto.Question> questionOptional = questions.stream().filter(question -> question.getHeading().equals(AppProperties.SUBJECT_ANS_HEADING_METHOD)).findFirst();
            String ansMethod = subjectItem.getAnsMethod();
            questionList.add(new ItemCorrectResp.Question(AppProperties.SUBJECT_ANS_HEADING_METHOD, questionOptional.map(ItemCorrectDto.Question::getAns).orElse(null), ansMethod));
            if (questionOptional.isPresent()) {
                ItemCorrectDto.Question question = questionOptional.get();
                if (this.checkSubjectAns(question.getAns(), ansMethod)) {
                    score += scoreMap.get(subjectItem.getDifficultyId());
                }
            }
        }
        if (StringUtils.hasText(subjectItem.getAnsPrescription())) {
            Optional<ItemCorrectDto.Question> questionOptional = questions.stream().filter(question -> question.getHeading().equals(AppProperties.SUBJECT_ANS_HEADING_PRESCRIPTION)).findFirst();
            String ansPrescription = subjectItem.getAnsPrescription();
            questionList.add(new ItemCorrectResp.Question(AppProperties.SUBJECT_ANS_HEADING_PRESCRIPTION, questionOptional.map(ItemCorrectDto.Question::getAns).orElse(null), ansPrescription));
            if (questionOptional.isPresent()) {
                ItemCorrectDto.Question question = questionOptional.get();
                if (this.checkSubjectAns(question.getAns(), ansPrescription)) {
                    score += scoreMap.get(subjectItem.getDifficultyId());
                }
            }
        }
        if (StringUtils.hasText(subjectItem.getAnsMedicine())) {
            Optional<ItemCorrectDto.Question> questionOptional = questions.stream().filter(question -> question.getHeading().equals(AppProperties.SUBJECT_ANS_HEADING_MEDICINE)).findFirst();
            String ansMedicine = subjectItem.getAnsMedicine();
            questionList.add(new ItemCorrectResp.Question(AppProperties.SUBJECT_ANS_HEADING_MEDICINE, questionOptional.map(ItemCorrectDto.Question::getAns).orElse(null), ansMedicine));
            if (questionOptional.isPresent()) {
                ItemCorrectDto.Question question = questionOptional.get();
                if (this.checkSubjectAns(question.getAns(), ansMedicine)) {
                    score += scoreMap.get(subjectItem.getDifficultyId());
                }
            }
        }
        // 构造结果集
        resp.setScore(score);
        resp.setQuestions(questionList);
        resp.setTotalScore(calculateItemTotalScore(subjectItem));
        return resp;

    }

    /**
     * 检查主观题答案
     * @param stuAnswer      该问学生答案
     * @param standardAnswer 该问标准答案
     * @return 是否正确
     */
    private boolean checkSubjectAns(String stuAnswer, String standardAnswer) {
        /*
            诊断、病机、治法、方药答案关键字。
            “|”区分一个答题点相关的多个关键字，只要答出其中一个关键字，则此答题点判定命中。
            “;”区分多个答题点，每个答题点都必须踩到本项才可得分。
        */
        if (!StringUtils.hasText(stuAnswer)) {
            return false;
        }
        boolean flag = true;
        String[] points = standardAnswer.split(";");
        for (String point : points) {
            // 分词获得该答题点的多个关键词
            String[] ansList = point.split("\\|");
            boolean pointFlag = false;// 标记该答题点是否命中
            for (String s : ansList) {
                if (stuAnswer.contains(s)) {
                    pointFlag = true;
                    break;// 命中其中一个关键字则当前答题点正确
                }
            }
            if (!pointFlag) {
                // 如果其中一个答题点不正确，则该问题答案不正确
                flag = false;
                break;
            }
        }
        return flag;
    }

    /**
     * 评判填空题
     * @param dto  题目答案封装
     * @param rank 题目答案等级, all表示全部
     * @return 评判结果
     */
    @Override
    public ItemCorrectResp correctClozeItem(ItemCorrectDto dto, String rank) {
        // 获得分数矩阵
        Map<Long, Map<String, Integer>> scoreMap = AppProperties.ITEM_SCORE_CLOZE;
        // 查找题目实体类
        ClozeItem clozeItem = clozeItemService.getClozeItemById(dto.getItemId());
        if (Objects.isNull(clozeItem)) {
            throw new EntityInvalidException("题目不存在");
        }
        // 构造结果集
        ItemCorrectResp resp = new ItemCorrectResp();
        BeanUtils.copyProperties(dto, resp, "questions");
        resp.setContent(clozeItem.getContent());
        resp.setQuestions(Collections.emptyList());
        // 分数计数器
        int score = 0;
        String[] ans = clozeItem.getAns().split("\\|");
        List<String> answer;// 后面判题答案
        if (rank.equals(AppProperties.CLOZE_ITEM_ANS_RANK_ALL)) {
            answer = Arrays.asList(ans);
        } else {
            answer = Arrays.stream(ans).filter(s -> rank.contains(s.substring(0, 1))).collect(Collectors.toList());
        }

        /* 逻辑一 答案里面包含则正确 */
//        for (String s : ans) {
//            String rank = s.substring(0, 1);// 获取答案难度
//            String a = s.substring(1);// 获取答案
//            if (dto.getAns().contains(a)) {
//                score += scoreMap.get(clozeItem.getDifficultyId()).get(rank);
//            }
//        }
        /* 逻辑二 每个空按顺序评判 */
        for (int i = 0; i < answer.size(); i++) {
            String s = answer.get(i);
            String r = s.substring(0, 1);// 获取答案难度
            String a = s.substring(1);// 获取答案
            if (dto.getAns().get(i).equals(a)) {
                score += scoreMap.get(clozeItem.getDifficultyId()).get(r);
            }
        }
        resp.setStandardAns(Arrays.asList(ans));
        resp.setScore(score);
        resp.setTotalScore(calculateItemTotalScore(clozeItem));
        return resp;

    }

    /**
     * 评判每日一练题目答案
     * @param dtos       答案评判数据封装
     * @param currUserId 当前登录用户ID
     * @return 评判结果
     */
    @Override
    @Transactional
    public List<ItemCorrectResp> correctItemBatchByDailyExercise(List<ItemCorrectDto> dtos, Long currUserId) {
        // 查询每日一练信息
        StudentDailyExerciseBo bo = redisUtil.getCacheObject(CachePrefix.STU_DAILY_EXERCISE_CACHE.getCacheKey(currUserId.toString()));
        // 判断是否已完成
        if (bo.getIsComplete().equals(1)) {
            return null;
        }
        // 构造结果集
        List<ItemCorrectResp> respList = new ArrayList<>();
        // 判题并给出分数
        for (ItemCorrectDto dto : dtos) {
            ItemCorrectResp resp;
            if (dto.getType().equals(AppProperties.ITEM_TYPE_SUBJECT)) {// 判断题目类型
                resp = this.correctSubjectItem(dto);
            } else {
                resp = this.correctClozeItem(dto, AppProperties.CLOZE_ITEM_ANS_RANK_ALL);
            }
            respList.add(resp);
        }
        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 更新学生积分
        ScoreUpdateDto scoreUpdateDto = new ScoreUpdateDto();
        scoreUpdateDto.setType(1);
        scoreUpdateDto.setAccount(userCacheBo.getAccount());
        int score = respList.stream().mapToInt(ItemCorrectResp::getScore).sum();
        scoreUpdateDto.setNum(score);
        if (!scoreService.saveScore(scoreUpdateDto, currUserId)) {
            throw new DatabaseOperationException("数据库操作失败");
        }
        bo.setIsComplete(1);
        bo.setScore(score);
        redisUtil.setCacheObject(CachePrefix.STU_DAILY_EXERCISE_CACHE.getCacheKey(currUserId.toString()), bo, getTimeToTheMidnight(), TimeUnit.SECONDS);
        return respList;
    }

    /**
     * 获取距离当天24点的时间
     * @return 距离当天24点的时间
     */
    private Integer getTimeToTheMidnight() {
        // 计算距离当天24点还有多长时间
        LocalDateTime now = LocalDateTime.now(); // 获取当前时间
        LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay(); // 获取今天24点的时间
        return new Long(now.until(midnight, ChronoUnit.SECONDS)).intValue();// 计算距离今天24点还有多少秒
    }

    /**
     * 评判发布试卷练习答案
     * @param dto        试卷答案DTO
     * @param currUserId 当前用户ID
     * @return 判题结果
     * @throws JsonProcessingException Json序列化异常
     */
    @Override
    @Transactional
    public List<ItemCorrectResp> correctItemBatchByExercise(CorrectExerciseDto dto, Long currUserId) throws JsonProcessingException {
        // 获取当前用户信息
        UserCacheBo userCacheBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(currUserId.toString()));
        // 校验发布练习是否存在或过期
        Exercise exercise = exerciseService.getById(dto.getExerciseId());
        if (Objects.isNull(exercise)) {
            return null;// 练习不存在
        }
        // 判断是否在练习的提交时间内
        Date now = new Date();
        if (now.before(exercise.getEnableTime()) || now.after(exercise.getDisableTime())) {
            throw new EntityInvalidException("未到开启时间或已过期");
        }

        // 获取练习学生关系
        ExerciseStudent exerciseStudent = exerciseService.getExerciseStudent(exercise.getId(), userCacheBo.getAccount());
        if (Objects.isNull(exerciseStudent)) {
            return null;
        }
        // 判断练习是否已提交过
        if (exerciseStudent.getCompleteStatus().equals(1)) {
            throw new EntityInvalidException("请勿重复提交");
        }

        // 获取关联试卷
        List<TestPaperDetail> testPaperDetails = testPaperDetailMapper.selectList(new LambdaQueryWrapper<TestPaperDetail>().eq(TestPaperDetail::getTestPaperId, exercise.getTestPaperId()));
        // 判题
        List<ItemCorrectResp> respList = new ArrayList<>();
        List<ItemCorrectDto> dtos = dto.getDtos();
        for (ItemCorrectDto itemCorrectDto : dtos) {
            if (itemCorrectDto.getType().equals(AppProperties.ITEM_TYPE_SUBJECT)) {
                respList.add(this.correctSubjectItem(itemCorrectDto));
            } else {
                testPaperDetails
                        .stream()
                        .filter(detail -> detail.getItemId().equals(itemCorrectDto.getItemId()))
                        .findFirst()
                        .ifPresent(detail -> respList.add(this.correctClozeItem(itemCorrectDto, detail.getRank())));
            }
        }

        /* 逻辑一：允许提交多次
        // 保存答题记录
        // 查询记录是否存在
        ExerciseHisUpdateDto exerciseHisUpdateDto = exerciseHisService.getExerciseHis(exercise.getId(), userCacheBo.getAccount());
        if (Objects.isNull(exerciseHisUpdateDto)) {
            // 如果记录不存在则创建
            exerciseHisUpdateDto = exerciseHisService.initExerciseUpdateDto(exercise.getId(), userCacheBo.getAccount(), exercise.getTestPaperId(), testPaperDetails.size());
            if (!exerciseHisService.saveExerciseHis(exerciseHisUpdateDto, currUserId)) {
                throw new DatabaseOperationException("数据库操作失败");
            }
        }
        if (CollectionUtils.isEmpty(exerciseHisUpdateDto.getDetails())) {
            exerciseHisUpdateDto.setDetails(Collections.emptyList());
        }
        List<ExerciseHisUpdateDto.Detail> details = exerciseHisUpdateDto.getDetails();
        // 保存题目记录
        for (ItemCorrectResp resp : respList) {
            Optional<ExerciseHisUpdateDto.Detail> optional = details.stream().filter(detail -> detail.getItemId().equals(resp.getItemId())).findFirst();
            if (!optional.isPresent()) {// 如果题目记录不存在则保存记录并保存得分
                ExerciseHisDetailUpdateDto exerciseHisDetailUpdateDto = new ExerciseHisDetailUpdateDto();
                exerciseHisDetailUpdateDto.setExerciseHisId(exercise.getId());
                exerciseHisDetailUpdateDto.setItemId(resp.getItemId());
                exerciseHisDetailUpdateDto.setExerciseHisId(exerciseHisUpdateDto.getExerciseId());
                exerciseHisDetailUpdateDto.setItemType(resp.getType());
                exerciseHisDetailUpdateDto.setAns(ItemUtil.getUserItemJsonAns(resp));
                if (!exerciseHisService.saveExerciseHisDetail(exerciseHisDetailUpdateDto, currUserId)) {
                    throw new DatabaseOperationException("数据库操作失败");
                }
                // 保存得分
                ScoreUpdateDto scoreUpdateDto = new ScoreUpdateDto();
                scoreUpdateDto.setNum(resp.getScore());
                scoreUpdateDto.setAccount(userCacheBo.getAccount());
                scoreUpdateDto.setType(2);
                if (!scoreService.saveScore(scoreUpdateDto, currUserId)) {
                    throw new DatabaseOperationException("更新积分失败");
                }
            }
        }
        // 判断题目完成情况
        List<Long> ids = exerciseHisService.getExerciseHis(exercise.getId(), userCacheBo.getAccount()).getDetails().stream().map(ExerciseHisUpdateDto.Detail::getItemId).collect(Collectors.toList());
        if (ids.size() == testPaperDetails.size()){
            if (!exerciseService.completeExercise(exercise.getId(), userCacheBo.getAccount()) || !exerciseHisService.completeExerciseHis(exercise.getId(), userCacheBo.getAccount())) {
                throw new DatabaseOperationException("提交失败");
            }
        }
         */

        /* 逻辑二：只允许提交一次 */

        // 保存答题记录
        ExerciseHis exerciseHis = new ExerciseHis();
        exerciseHis.setExerciseId(exercise.getId())
                .setAccount(userCacheBo.getAccount())
                .setType(2)
                .setTestPaperId(exercise.getTestPaperId())
                .setCompletionStatus(1)
                .setScore(respList.stream().mapToInt(ItemCorrectResp::getScore).sum())
                .setConsumeTime(dto.getSeconds())
                .setCorrectCount(new Long(respList.stream().filter(resp -> resp.getScore().equals(resp.getTotalScore())).count()).intValue())
                .setItemCount(testPaperDetails.size())
                .setCreateUser(currUserId)
                .setUpdateUser(currUserId);
        if (!exerciseHisService.save(exerciseHis) || !exerciseService.completeExercise(exercise.getId(), userCacheBo.getAccount())) {
            throw new DatabaseOperationException("保存失败");
        }
        // 保存答题详细记录
        for (ItemCorrectResp resp : respList) {
            ExerciseHisDetail detail = new ExerciseHisDetail();
            detail.setExerciseHisId(exerciseHis.getId())
                    .setItemId(resp.getItemId())
                    .setItemType(resp.getType())
                    .setAns(ItemUtil.getUserItemJsonAns(resp))
                    .setScore(resp.getScore())
                    .setCreateUser(currUserId)
                    .setUpdateUser(currUserId);
            if (!exerciseHisService.saveExerciseHisDetail(detail)) {
                throw new DatabaseOperationException("保存详细信息失败");
            }
            // 保存得分
            ScoreUpdateDto scoreUpdateDto = new ScoreUpdateDto();
            scoreUpdateDto.setNum(resp.getScore());
            scoreUpdateDto.setAccount(userCacheBo.getAccount());
            scoreUpdateDto.setType(2);
            if (!scoreService.saveScore(scoreUpdateDto, currUserId)) {
                throw new DatabaseOperationException("更新积分失败");
            }
        }
        return respList;
    }

}
